/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/

/**
 * @brief  Helper for translating expressions.
 *
 * This file is ONLY included by emitact_expr.c
 *
 * @author  WXQ#XDU
 * @date    2025.01
 */

#pragma once


#include "typesys.h"
#include "_emit_inner.h"



// Pass attributes of the specified child to this node
//
t_codeattr_ptr emit_passAttributeOfChild__( t_tree_ptr node, int idxChild )
{
    t_tree_ptr      childNode = tree_get_child(node, idxChild);
    t_codeattr_ptr  childAttr = (t_codeattr_ptr)tree_get_annotation(childNode);
    if (NULL == childAttr)
        return NULL;

    // Just return the reference with a counter
    t_codeattr_ptr  myAttr    = codeattr_getReference(childAttr);
    tree_set_annotation(node, myAttr);
    return myAttr;
}


// Get the arithmetic value(address) of an expression.
//
t_ir_address_ptr  emit_getExprAddress__( t_codemgr_ptr   codemgr,
                                         t_codeattr_ptr  E_attr )  // in, out
{
    if ( EXPR_IS_ARITH(E_attr) ) {      // arithmetic expr
        if ( E_attr->offset == NULL )   // read a scalar
            return E_attr->place;

        // For reading array element, emit  t1 = V.place[V.offset]
        t_ir_address_ptr tempVar = codemgr_newTempVar(codemgr);
        t_ircode_ptr codePtr = codemgr_emit(codemgr, OP_ARR_READ,
                                E_attr->place, E_attr->offset,
                                tempVar);
        codePtr->comment.text = CODEDESC_ARR_READE;
        return tempVar;
    }

    // For reading a boolean expr, we need its value (address), so
    // emit codes that computing its value :
    //      (i)    t1 = 1
    //      (i+1)  goto i+3
    //      (i+2)  t1 = 0
    // and:
    //      backpatch( E.tc, i    );
    //      backpatch( E.fc, i + 2);

    // emit:  t1=1
    t_ir_address_ptr  resultAddr = codemgr_newTempVar(codemgr);
    t_ircode_ptr codeTrue = codemgr_emit(codemgr, OP_ASSIGN,
                            codemgr->trueAddress, NULL, resultAddr);
    codeTrue->comment.text = CODEDESC_BOOL2VAL "(1)";

    // emit: goto i+3
    t_ircode_ptr codeJump = codemgr_emit(codemgr, OP_GOTO, NULL, NULL,
                            codemgr_newNumber(codemgr, codeTrue->id + 3) );
    codeJump->comment.text = CODEDESC_BOOL2VAL "(2)";

    // emit: t1=0
    t_ircode_ptr codeFalse = codemgr_emit(codemgr, OP_ASSIGN,
                            codemgr->falseAddress, NULL, resultAddr);
    codeFalse->comment.text = CODEDESC_BOOL2VAL "(3)";

    emitter_backpatch(E_attr->trueChain,  codeTrue->id,  "B2A" CHAIN_TC);
    emitter_backpatch(E_attr->falseChain, codeFalse->id, "B2A" CHAIN_FC);

    E_attr->place  = resultAddr;
    E_attr->offset = NULL;
    E_attr->type   = type_new_baseType(TK_TYPE_INT);
    return resultAddr;
}


// Convert an arithmetic expr to boolean expr.
// This function will emit code like:
//   (k)    if E.place goto -  ==>> .tc = make-chain
//   (k+1)  goto -             ==>> .fc = make-chain
// and return  k.
//
// See the action of production "E -> id" in section 4.8.5 of textbook.
//
t_ircode_id  emit_convertExprToBool__(  t_codemgr_ptr  codemgr,
                                        t_codeattr_ptr operandAttr,
                                        t_codeattr_ptr resultAttr )  // out
{
    assert ( EXPR_IS_ARITH(operandAttr) );

    t_ircode_ptr gotoTrue  = codemgr_emit( codemgr,   OP_GOTO_TRUE,
                                operandAttr->place,   NULL,
                                codemgr_unknownJumpAddress(codemgr) );
    t_ircode_ptr gotoFalse = codemgr_emit( codemgr,   OP_GOTO,
                                NULL,   NULL,
                                codemgr_unknownJumpAddress(codemgr) );
    resultAttr->trueChain  = emitter_makeChain( gotoTrue , "A2B" CHAIN_TC );
    resultAttr->falseChain = emitter_makeChain( gotoFalse, "A2B" CHAIN_FC );
    gotoTrue->comment.text  = CODEDESC_VAL2BOOL "(1)," CODEDESC_BACKPATCHED;
    gotoFalse->comment.text = CODEDESC_VAL2BOOL "(2)," CODEDESC_BACKPATCHED;

    // flag the result is a boolean expr
    resultAttr->place = resultAttr->offset = NULL;
    resultAttr->type  = type_new_baseType(TK_TYPE_INT);

    return gotoTrue->id;
}



// Emit codes for assignment:  leftValue = E.
// The result attribute must be of leftValue to support x = y = ... = E.
//
static void emit_assign__( t_emitter_ptr  pEmitter,
                           t_codeattr_ptr leftValueAttr,
                           t_codeattr_ptr rightValueAttr,
                           t_codeattr_ptr resultAttr )   // out
{
    t_ir_address_ptr    rightAddr
            = emit_getExprAddress__(pEmitter->ircodemgrPtr, rightValueAttr);

// CASE 1:  For update a scalar, emit: V.place = E.place
    if (NULL == leftValueAttr->offset) {
        codemgr_emit( pEmitter->ircodemgrPtr,  OP_ASSIGN,
                     rightAddr, NULL,
                     leftValueAttr->place );
        resultAttr->place  = leftValueAttr->place;
        resultAttr->offset = 0;

        return;
    }

// CASE 2: For update array element,  such as  arr[idx][idx] = E.place,
// emit:  V.place[V.offset] = E.place
    t_ircode_ptr  codePtr = codemgr_emit( pEmitter->ircodemgrPtr,
                    OP_ARR_WRITE,
                    leftValueAttr->place,
                    leftValueAttr->offset,
                    rightAddr );
    codePtr->comment.text = CODEDESC_ARR_WRITE;
    resultAttr->place  = leftValueAttr->place;
    resultAttr->offset = leftValueAttr->offset;
}


// Emit codes for assignment with operation, such as  leftValue += E
// The result attribute must be of leftValue to support x = y = ...  += E.
//
static void emit_assign_withop__( t_emitter_ptr   pEmitter,
                                  EnumOpCode      opCode,
                                  t_codeattr_ptr  leftValueAttr,
                                  t_codeattr_ptr  rightValueAttr,
                                  t_codeattr_ptr  resultAttr )    // out
{
    t_ir_address_ptr    rightAddr
            = emit_getExprAddress__(pEmitter->ircodemgrPtr, rightValueAttr);
    t_ircode_ptr     codePtr;

// CASE 1:  For update a scalar, such as  x += y,
//   emit:      t1 = x + y,
//              x  = t1
    if (NULL == leftValueAttr->offset) {
        t_ir_address_ptr  tempAddr = codemgr_newTempVar(pEmitter->ircodemgrPtr);
        codePtr = codemgr_emit( pEmitter->ircodemgrPtr,    opCode,
                                leftValueAttr->place, rightAddr, tempAddr );
        codePtr->comment.text = CODEDESC_OPASSIGN "(1)";

        codePtr = codemgr_emit( pEmitter->ircodemgrPtr,    OP_ASSIGN,
                            tempAddr, NULL,  leftValueAttr->place );
        codePtr->comment.text = CODEDESC_OPASSIGN "(2)";

        resultAttr->place  = leftValueAttr->place;
        resultAttr->offset = 0;

        return;
    }

// CASE 2:  For update array element, such as  arr[idx][idx] op= E.place,
//   emit:      t1 = V.place[V.offset]
//              t1 = t1 op E
//              V.place[V.offset] = t1
    t_ir_address_ptr  tempAddr = codemgr_newTempVar(pEmitter->ircodemgrPtr);
    codePtr = codemgr_emit( pEmitter->ircodemgrPtr,     OP_ARR_READ,
                            leftValueAttr->place,  leftValueAttr->offset,
                            tempAddr );
    codePtr->comment.text = CODEDESC_OPASSIGN_ARR " (1): " CODEDESC_ARR_READE;

    codePtr = codemgr_emit( pEmitter->ircodemgrPtr,     opCode,
                            tempAddr,  rightAddr,  tempAddr );
    codePtr->comment.text = CODEDESC_OPASSIGN_ARR " (2) ";

    codePtr = codemgr_emit( pEmitter->ircodemgrPtr,     OP_ARR_WRITE,
                            leftValueAttr->place,  leftValueAttr->offset,
                            tempAddr );
    codePtr->comment.text = CODEDESC_OPASSIGN_ARR " (3): " CODEDESC_ARR_WRITE;

    resultAttr->place  = leftValueAttr->place;
    resultAttr->offset = leftValueAttr->offset;
}



// Emit codes for binary boolean operations, x && y, x || y.
// Apply the Short-Circuit evaluating  style
// according to section 4.8.5  in our textbook.
//
static void  emit_booleanOp2__( t_codemgr_ptr   codemgr,
                                EnumOpCode      opCode,
                                t_tree_ptr      optrNode,
                                t_codeattr_ptr  leftAttr,
                                t_codeattr_ptr  rightAttr,
                                t_codeattr_ptr  resultAttr)
{
    // attribute of this operator is computed when visit "&&" and "||"
    // by emit_visit_terminal()
    t_codeattr_ptr optrAttr = (t_codeattr_ptr) tree_get_annotation(optrNode);

    // first code of right operand
    t_ircode_id  rightFirstCode = optrAttr == NULL ? 0 : optrAttr->firstCodeId;

    if ( OP_OR == opCode ) {    //   E1 || M E2
        // Step 1:   backpatch( E1.fc, M.stat )
        emitter_backpatch(leftAttr->falseChain, rightFirstCode, "E1" CHAIN_FC);
        // Step 2:   E.tc = merge( E2.tc, E1.tc )
        resultAttr->trueChain = emitter_mergeChain(
                            rightAttr->trueChain, "E2" CHAIN_TC,
                            leftAttr->trueChain, "E1" CHAIN_TC,
                            NULL, "E" CHAIN_TC);
        // Step 3:  E.fc = E2.fc
        ASSIGN_CHAIN( resultAttr->falseChain ,  "E"  CHAIN_FC,
                      rightAttr->falseChain  ,  "E2" CHAIN_FC  );
    }
    else {                      //   E1 && M E2
        // Step 1:   backpatch( E1.tc, M.stat )
        emitter_backpatch(leftAttr->trueChain, rightFirstCode, "E1" CHAIN_TC);
        // Step 2:   E.fc = merge( E2.fc, E1.fc )
        resultAttr->falseChain = emitter_mergeChain(
                            rightAttr->falseChain, "E2" CHAIN_FC,
                            leftAttr->falseChain,  "E1" CHAIN_FC,
                            NULL, "E" CHAIN_FC);
        // Step 3:  E.tc = E2.tc
        ASSIGN_CHAIN( resultAttr->trueChain ,  "E"  CHAIN_TC,
                      rightAttr->trueChain  ,  "E2" CHAIN_TC);
    }

    // flag the result as a boolean expr
    resultAttr->place = resultAttr->offset = NULL;
    resultAttr->type  = type_new_baseType(TK_TYPE_INT);
}



// Emit codes for relational operations, such as x>y, x==y.
//
// Apply the Short-Circuit evaluating style  according to the action
// of production "E-> id1 relop id2" in section 4.8.5 in our textbook.
//
static void emit_relationOp2__( t_codemgr_ptr   codemgr,
                                EnumOpCode      opCode,
                                t_codeattr_ptr  leftAttr,
                                t_codeattr_ptr  rightAttr,
                                t_codeattr_ptr  resultAttr )
{
    // In X == Y, both X and Y may be a boolean-expr, so
    //   convert boolean-expr to arithmetic-expr
    t_ir_address_ptr leftAddress  = emit_getExprAddress__(codemgr, leftAttr);
    t_ir_address_ptr rightAddress = emit_getExprAddress__(codemgr, rightAttr);

    // emit:  if  x relop y goto -    ===>  .tc = make-chain
    //        goto -                  ===>  .fc = make-chain
    t_ircode_ptr trueCode   = codemgr_emit( codemgr,     opCode ,
                                leftAddress, rightAddress,
                                codemgr_unknownJumpAddress(codemgr) );
    t_ircode_ptr falseCode  = codemgr_emit( codemgr,     OP_GOTO,
                                NULL, NULL,
                                codemgr_unknownJumpAddress(codemgr) );
    resultAttr->trueChain   = emitter_makeChain( trueCode , "relop" CHAIN_TC );
    resultAttr->falseChain  = emitter_makeChain( falseCode, "relop" CHAIN_FC);
    trueCode->comment.text  = CODEDESC_BACKPATCHED;
    falseCode->comment.text = CODEDESC_BACKPATCHED;

    // flag the result as a boolean expr
    resultAttr->place = resultAttr->offset = NULL;
    resultAttr->type  = type_new_baseType(TK_TYPE_INT);
}



// Get op-code by a token kind
#define OPKIND_TO_OPCODE(listNode)      do {                    \
    optrNode = (t_tree_ptr)list_node_get_value(listNode);       \
    EnumTokenKind  opTokenKind = tree_get_tokenKind(optrNode);  \
    switch ( opTokenKind ) {                                    \
    case TK_OP_PLUS:  opCode = OP_PLUS ;  isBooleanOp = isRelationOp = false; break; \
    case TK_OP_MINUS: opCode = OP_MINUS;  isBooleanOp = isRelationOp = false; break; \
    case TK_OP_MUL:   opCode = OP_MUL  ;  isBooleanOp = isRelationOp = false; break; \
    case TK_OP_DIV:   opCode = OP_DIV  ;  isBooleanOp = isRelationOp = false; break; \
    case TK_OP_MOD:   opCode = OP_MOD  ;  isBooleanOp = isRelationOp = false; break; \
\
    case TK_OP_AND:   opCode = OP_AND  ;  isBooleanOp =true; isRelationOp = false; break;   \
    case TK_OP_OR:    opCode = OP_OR   ;  isBooleanOp =true; isRelationOp = false; break;   \
\
    case TK_OP_EQ:    opCode = OP_GOTO_EQ ;  isRelationOp=true; isBooleanOp = false; break; \
    case TK_OP_NE:    opCode = OP_GOTO_NE ;  isRelationOp=true; isBooleanOp = false; break; \
    case TK_OP_GT:    opCode = OP_GOTO_GT ;  isRelationOp=true; isBooleanOp = false; break; \
    case TK_OP_GE:    opCode = OP_GOTO_GE ;  isRelationOp=true; isBooleanOp = false; break; \
    case TK_OP_LT:    opCode = OP_GOTO_LT ;  isRelationOp=true; isBooleanOp = false; break; \
    case TK_OP_LE:    opCode = OP_GOTO_LE ;  isRelationOp=true; isBooleanOp = false; break; \
    default:    opCode = OP_UNKNOWN__; isBooleanOp = isRelationOp = false; break;   \
    }  } while (0)


// Emit codes for all binary operators except assignments.
//
// This function applies expressions that contain one or more
// binary operators with the same priority and left-associative,
// such as  x + y - z,  the code list is like:
//    t1 = x + y
//    t2 = t1 - z
//
static void emit_binaryOptr_loop_(t_tree_listener_ptr listenerPtr, t_tree_ptr node)
{
    t_emitter_ptr     pEmitter = (t_emitter_ptr)listenerPtr;

// CASE 1: for an expression without operator, E1 -> E2,
//         just pass attribute in bottom-up.
    if (tree_get_childCount(node) < 2) {
        emit_passAttributeOfChild__(node, 0);
        return;
    }

    t_list_iterator  itr;     // prepare to visit operands and operators
    LIST_ITERATOR_INIT ( &itr, tree_get_children(node) );
    t_list_node_ptr subNode = LIST_ITERATOR_BEGIN ( &itr ); // first operand
    t_list_node_ptr end     = LIST_ITERATOR_END   ( &itr );

    t_tree_ptr     optrNode;  // the operator node in parse tree
    t_tree_ptr     rightNode, leftNode = (t_tree_ptr)list_node_get_value(subNode);
    t_codeattr_ptr rightAttr, leftAttr = (t_codeattr_ptr)tree_get_annotation(leftNode);

    bool  isBooleanOp  = false;  // the operator is &&, ||
    bool  isRelationOp = false;  // the operator is ==, !=, >, ...
    EnumOpCode  opCode;  {       // set these 3 flags
        subNode = LIST_ITERATOR_NEXT(&itr);  // get first operator
        OPKIND_TO_OPCODE ( subNode );
    }

    subNode = LIST_ITERATOR_NEXT(&itr);     // the second expression

// CASE 2:  translate arithmetic expr,  x op y op z,
//          according to section 4.3.2  in our textbook.
    if ( ! isRelationOp && ! isBooleanOp ) {
        t_ir_address_ptr tempAddr,  rightAddr;
        t_ir_address_ptr leftAddress = emit_getExprAddress__( pEmitter->ircodemgrPtr, leftAttr);

        for ( ; subNode != end; subNode = LIST_ITERATOR_NEXT(&itr) ) {
            rightNode = (t_tree_ptr) list_node_get_value(subNode);
            rightAttr = (t_codeattr_ptr) tree_get_annotation(rightNode);
            // emit:  t1 = x op y
            tempAddr  = codemgr_newTempVar(pEmitter->ircodemgrPtr);
            rightAddr = emit_getExprAddress__( pEmitter->ircodemgrPtr, rightAttr);
            codemgr_emit( pEmitter->ircodemgrPtr,  opCode,
                         leftAddress,  rightAddr,  tempAddr );
            leftAddress = tempAddr;
            subNode = LIST_ITERATOR_NEXT(&itr); // jump the operator
            if (subNode != end) {
                OPKIND_TO_OPCODE ( subNode );
            } else
                break;
        }

        t_codeattr_ptr myAttr = codeattr_new();
        myAttr->place = leftAddress;
        myAttr->type  = codemgr_getDataType(node);
        tree_set_annotation(node, myAttr);

        return;
    }

// CASE 3:  translate boolean and relational operations,
//          according to section 4.8  in our textbook,

    t_codeattr_ptr myAttr = codeattr_copy( leftAttr );
    myAttr->type  = codemgr_getDataType(node);
    tree_set_annotation(node, myAttr);

    for ( ; subNode != end; subNode = LIST_ITERATOR_NEXT(&itr) ) {
        rightNode = (t_tree_ptr) list_node_get_value(subNode);
        rightAttr = (t_codeattr_ptr) tree_get_annotation(rightNode);
        if ( isBooleanOp ) {
            emit_booleanOp2__( pEmitter->ircodemgrPtr, opCode, optrNode,
                            leftAttr,  rightAttr,  myAttr );
        }
        else {
            emit_relationOp2__( pEmitter->ircodemgrPtr, opCode,
                            leftAttr,  rightAttr,  myAttr );
        }

        leftAttr = myAttr;
        subNode = LIST_ITERATOR_NEXT(&itr); // the operator
        if (subNode != end) {
            OPKIND_TO_OPCODE ( subNode );
        } else
            break;
    }

    // end of function
}


// Emit codes for passing one argument in a function-call expression.
//
static int  emit_param__(t_list_app_value app_data, void * user_data)
{
    t_tree_ptr  argNode = (t_tree_ptr) app_data;
    if ( tree_is_markedByToken(argNode, TK_COMMA) )  // jump ","
        return 0;

    t_emitter_ptr    pEmitter = (t_emitter_ptr) user_data;
    t_codeattr_ptr   argAttr  = (t_codeattr_ptr) tree_get_annotation(argNode);
    t_ir_address_ptr argAddr  = emit_getExprAddress__( pEmitter->ircodemgrPtr, argAttr );

    // emit:  param E.place
    t_ircode_ptr codePtr = codemgr_emit(pEmitter->ircodemgrPtr,
                                OP_PARAM, argAddr, NULL, NULL);
    codePtr->comment.text = CODEDESC_PARAM;
    return 0;
}
